Serverless FrameworkでLambda Provisioned ConcurrencyのAutoScalingを実装する
今回はLambdaのProvisioned Concurrencyを指定された時間帯のみスケールアウトする環境をServerless Frameworkを使って実装する方法についてご相談いただきましたので、クラスメソッドらしくブログで回答したいと思います。
Provisioned Concurrency(おさらい)
Provisioned ConcurrencyはLambda関数の実行環境を予め待機させておくことで、コールドスタートによるスタートアップ時間を短縮し、処理時間の短縮や応答性の向上を実現する機能です。
実行時間あたりの料金は通常の料金よりも安く設定されていますが、一方で待機時間でも料金が発生する仕組みとなっています。
背景(前提)
- 特定の時間帯にリクエストが集中するサービスを提供している
- 待機時間コスト削減のためProvisioned Concurrencyを特定時間にのみスケールアウトさせたい
- Serverless Frameworkで実装したい
Provisioned ConcurrencyをAutoScalingさせる
以下がProvisioned Concurrencyを特定時間にスケールアウト、スケールインする環境のserverless.yml
です。
- 毎日15:00(JST)になると
scale-out
アクションが発動し、Provisioned Concurrencyが10
になる - 毎日15:05(JST)になると
scale-in
アクションが発動し、Provisioned Concurrencyが0
になる
service: as-provisionedconcurrency-sls frameworkVersion: '3' provider: name: aws runtime: python3.8 region: ap-northeast-1 stage: ${sls:stage} functions: hello: handler: handler.hello provisionedConcurrency: 1 resources: Resources: ScheduledConcurrency: Type: AWS::ApplicationAutoScaling::ScalableTarget Properties: MaxCapacity: 10 MinCapacity: 0 ResourceId: function:${self:service}-${self:provider.stage}-hello:provisioned RoleARN: arn:aws:iam::${aws:accountId}:role/aws-service-role/lambda.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_LambdaConcurrency ScalableDimension: lambda:function:ProvisionedConcurrency ScheduledActions: - ScalableTargetAction: MaxCapacity: 10 MinCapacity: 10 Schedule: 'cron(0 6 * * ? *)' ScheduledActionName: scale-out - ScalableTargetAction: MaxCapacity: 0 MinCapacity: 0 Schedule: 'cron(5 6 * * ? *)' ScheduledActionName: scale-in ServiceNamespace: lambda DependsOn: HelloProvConcLambdaAlias
以下のコマンドでデプロイします。(ステージを指定しない場合、デフォルトはdev
です)
serverless deploy --stage dev
以降はserverless.yml
内でポイントとなる点を取り上げて説明します。
provisionedConcurrency を指定する
functions: hello: handler: handler.hello provisionedConcurrency: 1
Provisioned Concurrencyを設定するにはfunctions:
内でprovisionedConcurrency:
を設定します。
後続で説明しますがAWS::ApplicationAutoScaling::ScalableTarget
を設定する際にLambda関数のバージョン、またはエイリアスの指定が必要になりますが$LATEST
は指定できません。そのため何らかのエイリアス作成が必要になりますがprovisionedConcurrency:
を指定することでprovisioned
というエイリアスが自動作成してくれます。
今回はhello:
を指定していますので、この場合に作成されるAWS::Lambda::Alias
の論理IDはHelloProvConcLambdaAlias
となります。
(余談)自前でAlias定義するのを諦めた話
注意点としては、provisionedConcurrency: 1
を指定しているため初回デプロイ〜初回のスケジュールアクションが発動するまではProvisioned Concurrencyが1
の状態が維持されますので待機コストが掛かります。provisionedConcurrency: 0
としてエイリアスだけ作成されることを期待しましたが、0
を指定した場合はエイリアスは作成されませんでした。
それならばresources:
内でAWS::Lambda::Alias
を自前で定義するか、、と試みましたがエイリアス定義に必要となるAWS::Lambda::Version
の論理IDにダイジェスト値(ハッシュ値)が含まれるようで、更にこのハッシュ値をserverless.yml内で取得する方法がよく判らない、、ということで私は諦めました。。
Application Auto Scalingの併用
Provisioned ConcurrencyのAutoScalingは、Application Auto Scalingを併用して実装します。
resources: Resources: ScheduledConcurrency: Type: AWS::ApplicationAutoScaling::ScalableTarget Properties: MaxCapacity: 10 MinCapacity: 0 ResourceId: function:${self:service}-${self:provider.stage}-hello:provisioned RoleARN: arn:aws:iam::${aws:accountId}:role/aws-service-role/lambda.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_LambdaConcurrency ScalableDimension: lambda:function:ProvisionedConcurrency
先述のとおりResourceId:
の指定にLambda関数名+バージョンまたはエイリアスの指定が必要です。provisionedConcurrency:
によって自動生成されたprovisioned
をエイリアス名に指定します。
Application Auto Scalingを設定するためのIAMロールが必要となりますので、RoleARN:
でサービスリンクされたIAMロールを指定します。存在しない場合でも、デフォルトで決まっているサービスリンクされたRoleARNの指定すれば自動的に作成されます。
明示的に依存関係を指定する
DependsOn: HelloProvConcLambdaAlias
依存関係としてエイリアスの論理IDを明示的に指定する必要があります。指定していない場合、以下のようなエラーになりました。
Error: CREATE_FAILED: ScheduledConcurrency (AWS::ApplicationAutoScaling::ScalableTarget) Validation failed for resource: function:as-provisionedconcurrency-sls-dev-hello:provisioned, scalable dimension: lambda:function:ProvisionedConcurrency. Reason: Cannot find alias arn: arn:aws:lambda:ap-northeast-1:123456789012:function:as-provisionedconcurrency-sls-dev-hello:provisioned (Service: AWSApplicationAutoScaling; Status Code: 400; Error Code: ValidationException; Request ID: abd268ba-ee09-4fb8-b4a5-57313c5460fe; Proxy: null)
動作確認
初回デプロイ後〜初回スケジュールアクションまでの間、先述のとおりProvisioned Concurrencyが1
の状態になります。
スケジュールによってscale-out
が発動し、しばらく待つとProvisioned Concurrencyが10
になりました。
スケジュールによってscale-in
が発動し、しばらく待つとProvisioned Concurrencyが0になりました。
以下、スケーリングアクティビティのログです。
$ aws application-autoscaling describe-scaling-activities --service-namespace lambda ScalingActivities: - ActivityId: afd024c2-5563-4dcf-a2a0-05be7e60b125 Cause: maximum capacity was set to 0 Description: Setting desired concurrency to 0. EndTime: '2023-03-16T16:35:50.939000+09:00' ResourceId: function:as-provisionedconcurrency-sls-dev-hello:provisioned ScalableDimension: lambda:function:ProvisionedConcurrency ServiceNamespace: lambda StartTime: '2023-03-16T16:35:16.628000+09:00' StatusCode: Successful StatusMessage: Successfully set desired concurrency to 0. Change successfully fulfilled by lambda. - ActivityId: 2953232c-1769-4cd4-8cc7-b5a3e90a2ee9 Cause: scheduled action name scale-in was triggered Description: Setting min capacity to 0 and max capacity to 0 EndTime: '2023-03-16T16:35:16.301000+09:00' ResourceId: function:as-provisionedconcurrency-sls-dev-hello:provisioned ScalableDimension: lambda:function:ProvisionedConcurrency ServiceNamespace: lambda StartTime: '2023-03-16T16:35:16.292000+09:00' StatusCode: Successful StatusMessage: Successfully set min capacity to 0 and max capacity to 0 - ActivityId: 8ff236d7-c52e-4707-9e92-352a62ff19e4 Cause: minimum capacity was set to 10 Description: Setting desired concurrency to 10. EndTime: '2023-03-16T16:32:51.738000+09:00' ResourceId: function:as-provisionedconcurrency-sls-dev-hello:provisioned ScalableDimension: lambda:function:ProvisionedConcurrency ServiceNamespace: lambda StartTime: '2023-03-16T16:30:43.762000+09:00' StatusCode: Successful StatusMessage: Successfully set desired concurrency to 10. Change successfully fulfilled by lambda. - ActivityId: 59828e6a-51da-44f0-8427-fe892ed998a3 Cause: scheduled action name scale-out was triggered Description: Setting min capacity to 10 and max capacity to 10 EndTime: '2023-03-16T16:30:43.421000+09:00' ResourceId: function:as-provisionedconcurrency-sls-dev-hello:provisioned ScalableDimension: lambda:function:ProvisionedConcurrency ServiceNamespace: lambda StartTime: '2023-03-16T16:30:43.396000+09:00' StatusCode: Successful StatusMessage: Successfully set min capacity to 10 and max capacity to 10 (省略)
まとめ
Provisioned Concurrencyはコールドスタートによる遅延を回避するために有効な手段ですが、ピーク時とピークアウト時のギャップが大きな場合、無駄な待機コストが発生する可能性があります。Application Auto Scalingを併用することで負荷やスケジュールによって自動的にスケールアウト/インが可能ですので、巧く利用して無駄なコストを削減してみてはいかがでしょうか。